﻿/**
 * Copyright (c) 2010, Microsoft Corporation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *       Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *       
 *       Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *       
 *       Neither the name of Microsoft nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Microsoft ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Microsoft BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System.Linq;
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;

namespace WebRole
{
    public class XDrives
    {
        const string XDRIVES_CONFIG_SETTING = "XDrives";
        const string XDRIVES_CONFIG_LOCAL_CACHE_SETTING = "XDrivesLocalCache";

        const string XDRIVES_KEY_LABEL = "xdrive_label";
        const string XDRIVES_KEY_BLOB_CONTAINER = "blob_container";
        const string XDRIVES_KEY_PAGE_BLOB_VHD = "page_blob_vhd";
        const string XDRIVES_KEY_SIZE_MB = "xdrive_sizeMB";
        const string XDRIVES_KEY_MOUNT_CACHE_SIZE_MB = "mount_cache_sizeMB";
        const string XDRIVES_KEY_CREATE_IF_NOT_EXIST = "create";
        const string XDRIVES_KEY_ACCESS_MODE = "access_mode";

        const string XDRIVES_ENV_VAR = "X_DRIVES";

        const string SETTING_STORAGE_ACCOUNT_INFO = "WindowsAzureStorageConnectionString";

        const string REGEX_BLOB_CONTAINER_NAME = @"^[a-z0-9][a-z0-9\-]{1,61}[a-z0-9]$";
        const string REGEX_BLOB_NAME = @"^[a-zA-Z0-9_\-\/\.\s]{1,1024}$";

        const int MIN_XDRIVES_SIZE_MB = 16;
        const int MAX_XDRIVES_SIZE_MB = 1000000;

        const int MAX_XDRIVES_MOUNTS_PER_INSTANCE = 16;

        const string ANSWER_YES = "yes";
        const string DEV_STORAGE_ACCT_PREFIX = "devstoreaccount1";

        private enum AccessMode
        {
            None,
            Write,
            ReadOnly
        };
        const string ACCESS_MODE_NONE = "none";
        const string ACCESS_MODE_WRITE = "write";
        const string ACCESS_MODE_READONLY = "readonly";

        private ICollection<IDictionary<string, string>> m_aXDriveConfigs = null;
        private CloudStorageAccount m_oCloudStorageAccount = null;
        private CloudBlobClient m_blobClient = null;
        private int m_iMaximumSizeInMegabytes = 0;
        private ICollection<string> m_aXDrivesPathMounted = null;

        /// <summary>
        /// Mount a VHDs based upon settings within ServiceConfiguration.cscfg file.
        /// </summary>
        public void MountXDrives()
        {
            try
            {
                if (!GetXDrivesConfig())
                {
                    return;
                }

                Trace.TraceInformation("Mounting X-Drives");
                if (!InitializeXDrives())
                {
                    return;
                }

                /*
                 * Interate over each X-Drive configuration and 
                 * perform setup as requested.
                 */
                Trace.TraceInformation("Mounting {0} X-Drives Configurations",
                    m_aXDriveConfigs.Count
                    );
                foreach (IDictionary<string, string> dicXDriveConfig in m_aXDriveConfigs)
                {
                    MountXDrive(dicXDriveConfig);
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError("Unexpected Exception: Failed to mount X-drives: {0): {1)",
                    ex.GetBaseException(),
                    ex.Message
                    );
            }
        }

        /// <summary>
        /// Unmount a VHDs based upon settings within ServiceConfiguration.cscfg file.
        /// </summary>
        public void UnmountXDrives()
        {
            try
            {
                /*
                 * X-Drives configuration string
                 * from ServiceConfiguration.cscfg file.
                 */
                string sXDrivesConfig = ReadXDrivesConfig();
                if (null == sXDrivesConfig || 0 == sXDrivesConfig.Length)
                {
                    return;
                }

                if (null == m_oCloudStorageAccount)
                {
                    return;
                }

                /*
                 * Get a listing of all X-Drives currently mounted.
                 */
                IDictionary<String, Uri> listDrives = CloudDrive.GetMountedDrives();
                if (0 == listDrives.Count)
                {
                    return;
                }

                Trace.TraceInformation("Unmounting X-Drives");

                /*
                 * Iterate over each mounted X-Drive and
                 * unmount them.
                 */
                foreach (KeyValuePair<String, Uri> kvpDrive in listDrives)
                {
                    string sXDriveLetter = GetDriveLetter(kvpDrive.Key);
                    Uri uriXDrivePath = kvpDrive.Value;

                    try
                    {
                        CloudDrive myCloudDrive = m_oCloudStorageAccount.CreateCloudDrive(
                            uriXDrivePath.ToString()
                        );

                        myCloudDrive.Unmount();
                        Trace.TraceInformation(
                            "X-Drive {0}: Unmounted {1}",
                            sXDriveLetter,
                            uriXDrivePath.ToString()
                            );
                    }
                    catch (CloudDriveException ex)
                    {
                        Trace.TraceError(
                            "X-Drive {0}: CloudDriveException: Failed to unmount {1}: {2}",
                            sXDriveLetter,
                            uriXDrivePath.ToString(),
                            ex.Message
                            );
                    }
                    catch (Exception ex)
                    {
                        Trace.TraceError(
                            "X-Drive {0}: Unexpected Exception: Failed to unmount {1}: {2}",
                            sXDriveLetter,
                            uriXDrivePath.ToString(),
                            ex.Message
                            );
                    }
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError(
                    "Unexpected Exception: Failed to unmount X-Drives: {0): {1)",
                    ex.GetBaseException(),
                    ex.Message
                    );
            }
        }

        /// <summary>
        /// Gather from ServiceConfiguration.cscfg, XDrives' configuration
        /// </summary>
        /// <returns>True if XDrives configuration is found and has contents.</returns>
        private bool GetXDrivesConfig()
        {
            /*
             * X-Drives configuration string
             * from ServiceConfiguration.cscfg file.
             */
            string sXDrivesConfig = ReadXDrivesConfig();
            if (null == sXDrivesConfig || 0 == sXDrivesConfig.Length)
            {
                return false;
            }

            /*
             * Parse provide XDrives string from Service Configuration file.
             */
            m_aXDriveConfigs = ParseXDriveConfigs(sXDrivesConfig);
            if (null == m_aXDriveConfigs || 0 == m_aXDriveConfigs.Count)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Initializes for mounting X-Drives
        /// </summary>
        private bool InitializeXDrives()
        {
            Trace.TraceInformation("Initializing for mounting X-Drives");

            /*
             * Cloud Storage Account
             */
            if (!GetCloudStorageAccount() || (null == m_oCloudStorageAccount) )
            {
                return false;
            }

            /*
             * Configuration: Get name of LocalStorage that will be used
             * for initializing X-Drives' mounts read cache size.
             */
            string sXDrivesLocalCacheName
                = RoleEnvironment.GetConfigurationSettingValue(XDRIVES_CONFIG_LOCAL_CACHE_SETTING).Trim();

            if (null == sXDrivesLocalCacheName || 0 == sXDrivesLocalCacheName.Length)
            {
                Trace.TraceWarning("No Local Cache for X-Drives was defined in Service Configuration file.");
                return false;
            }

            /*
             * Windows Azure Drive environment has to be initialized.
             * 
             * Create local storage which will act as the local cache
             * for all X-Drive mounts.
             */
            LocalResource localCache = RoleEnvironment.GetLocalResource(sXDrivesLocalCacheName);
            Char[] backSlash = { '\\' };
            String localCachePath = localCache.RootPath.TrimEnd(backSlash);
            CloudDrive.InitializeCache(localCachePath, localCache.MaximumSizeInMegabytes);

            m_iMaximumSizeInMegabytes = localCache.MaximumSizeInMegabytes;

            /*
             * Validate the sum of all request mount read cache size does
             * not exceed requested Local Cache size.
             */
            int iSumAllMountReadCacheSizeMB = XDrives.GetSumXDrivesMountCache(m_aXDriveConfigs);
            if (iSumAllMountReadCacheSizeMB > localCache.MaximumSizeInMegabytes)
            {
                Trace.TraceWarning(
                    "Sum of all X-Drives Mount Read Cache size {0} MB exceeds requested Local Cache size {1} MB",
                    iSumAllMountReadCacheSizeMB,
                    localCache.MaximumSizeInMegabytes
                    );
                return false;
            }

            /*
             * Get URI paths for all currently mounted X-Drives.
             */
            m_aXDrivesPathMounted = GetMountedDrivesPaths();

            /*
             * Validate: Maximum number of X-Drives has not been exceeded
             */
            if (MAX_XDRIVES_MOUNTS_PER_INSTANCE < m_aXDriveConfigs.Count)
            {
                Trace.TraceWarning(
                    "Number of X-Drives Mount request exceeds maximum {0}",
                    MAX_XDRIVES_MOUNTS_PER_INSTANCE
                    );
                return false;
            }

            /*
             * Create a new Blob service client
             */
            m_blobClient
                = m_oCloudStorageAccount.CreateCloudBlobClient();

            return true;
        }

        /// <summary>
        /// Setup X-Drive Configuration.
        /// </summary>
        /// <param name="dicXDriveConfig"></param>
        private bool MountXDrive(
            IDictionary<string, string> dicXDriveConfig
            )
        {
            try
            {
                string sXDriveLabel = string.Empty;
                string sBlobContainerName = string.Empty;
                string sPageBlobVhdName = string.Empty;
                AccessMode eAccessMode = AccessMode.None;
                bool bXDriveCreateIfNotExist = false;
                int iXDriveSizeMB = 0;
                int iXDriveMountCacheSizeMB = 0;

                if (!GetXDriveConfig(
                        dicXDriveConfig,
                        ref sXDriveLabel,
                        ref sBlobContainerName,
                        ref sPageBlobVhdName,
                        ref eAccessMode,
                        ref bXDriveCreateIfNotExist,
                        ref iXDriveSizeMB,
                        ref iXDriveMountCacheSizeMB
                        )
                ) {
                    return false;
                }

                /*
                 * Create a blob storage container
                 */
                if (!CreateXDriveBlobContainer(
                    sXDriveLabel,
                    sBlobContainerName
                    )
                ) {
                    return false;
                }

                /*
                 * Get a reference to a Windows Azure Drive
                 */
                CloudDrive oCloudDrive
                    = m_oCloudStorageAccount.CreateCloudDrive(
                        m_blobClient
                        .GetContainerReference(sBlobContainerName)
                        .GetPageBlobReference(sPageBlobVhdName)
                        .Uri.ToString()
                    );

                /*
                 * If requested, try creating X-Drive
                 * if not listed as mounted.
                 */
                if (bXDriveCreateIfNotExist)
                {
                    if (!CreateXDrive(
                        oCloudDrive,
                        sXDriveLabel,
                        sPageBlobVhdName,
                        iXDriveSizeMB
                        )
                    )
                    {
                        return false;
                    }
                }

                /*
                 * If a mount fails to get Write access because another
                 * already as write access (ERROR_LEASE_LOCKED), 
                 * then redo mount as ReadOnly.
                 */
                bool bReDo = false;
                if (!DoCloudDriveMount(oCloudDrive, sXDriveLabel, iXDriveMountCacheSizeMB, eAccessMode, ref bReDo))
                {
                    return false;
                }

                if (bReDo && !DoCloudDriveMount(oCloudDrive, sXDriveLabel, iXDriveMountCacheSizeMB, AccessMode.ReadOnly, ref bReDo))
                {
                    return false;
                }
            }
            catch (NullReferenceException ex)
            {
                Trace.TraceError("NullReferenceException: Failed to mount X-Drive: {0}",
                    ex.Message
                    );
                return false;
            }
            catch (Exception ex)
            {
                Trace.TraceError("Unexpected Exception: Failed to mount X-Drive: {0}",
                    ex.Message
                    );
                return false;
            }

            return true;
        }

        /// <summary>
        /// Get individual X-Drive configuration
        /// </summary>
        /// <param name="dicXDriveConfig">Dictionary of X-Drive configuration</param>
        /// <param name="sXDriveLabel">X-Drive friendly label</param>
        /// <param name="sBlobContainerName">Blob Container name</param>
        /// <param name="sPageBlobVhdName">Page Blob VHD name</param>
        /// <param name="eAccessMode">Access mode</param>
        /// <param name="bXDriveCreateIfNotExist">Create if Exists Flag</param>
        /// <param name="iXDriveSizeMB">X-Drive size in MB</param>
        /// <param name="iXDriveMountCacheSizeMB">X-Drive Mount read cache size in MB</param>
        /// <returns>True if X-Drive configuration is valid, else False</returns>
        private bool GetXDriveConfig(
            IDictionary<string, string> dicXDriveConfig,
            ref string sXDriveLabel,
            ref string sBlobContainerName,
            ref string sPageBlobVhdName,
            ref AccessMode eAccessMode,
            ref bool bXDriveCreateIfNotExist,
            ref int iXDriveSizeMB,
            ref int iXDriveMountCacheSizeMB
        ) {
            sXDriveLabel = string.Empty;
            sBlobContainerName = string.Empty;
            sPageBlobVhdName = string.Empty;
            eAccessMode = AccessMode.None;
            iXDriveSizeMB = 0;
            iXDriveMountCacheSizeMB = 0;
            bXDriveCreateIfNotExist = false;

            if (!dicXDriveConfig.ContainsKey(XDRIVES_KEY_LABEL)
                || !dicXDriveConfig.ContainsKey(XDRIVES_KEY_BLOB_CONTAINER)
                || !dicXDriveConfig.ContainsKey(XDRIVES_KEY_PAGE_BLOB_VHD)
            )
            {
                Trace.TraceWarning(
                    "Invalid XDrive configuration. Requires {0}, {1}, and {2} values.",
                    XDRIVES_KEY_LABEL,
                    XDRIVES_KEY_BLOB_CONTAINER,
                    XDRIVES_KEY_PAGE_BLOB_VHD
                    );
                return false;
            }

            /*
             * Configuration: Get X-Drive's Friendly name
             */
            sXDriveLabel = dicXDriveConfig[XDRIVES_KEY_LABEL];
            Trace.TraceInformation(
                "X-Drive {0}: Reading configuration",
                sXDriveLabel
                );

            /*
             * Configuration: Get Blob Container Name
             */
            sBlobContainerName = dicXDriveConfig[XDRIVES_KEY_BLOB_CONTAINER];

            /*
             * Validate: Blob Container Name's Length
             */
            if (sBlobContainerName.Length < 3 || sBlobContainerName.Length > 63)
            {
                Trace.TraceWarning(
                    "X-Drive {0}: {1} value {2}: Invalid length {3}: 3 to 63",
                    sXDriveLabel,
                    XDRIVES_KEY_BLOB_CONTAINER,
                    sBlobContainerName,
                    sBlobContainerName.Length
                    );
                return false;
            }

            /*
             * Validate: Blob Container Name's Format
             */
            Regex rPageBlobContainerName = new Regex(REGEX_BLOB_CONTAINER_NAME);
            Match matchPageBlobContainerName = rPageBlobContainerName.Match(sBlobContainerName);

            if (!matchPageBlobContainerName.Success)
            {
                Trace.TraceWarning(
                    "X-Drive {0}: {1} value {2}: Has invalid characters.",
                    sXDriveLabel,
                    XDRIVES_KEY_BLOB_CONTAINER,
                    sBlobContainerName
                    );
                return false;
            }

            /*
             * Configuration: Get Page Blob name associated with X-Drive
             */
            sPageBlobVhdName = dicXDriveConfig[XDRIVES_KEY_PAGE_BLOB_VHD];

            /*
             * Validate: Page Blob name's Length
             */
            if (sPageBlobVhdName.Length < 1 || sPageBlobVhdName.Length > 1024)
            {
                Trace.TraceWarning(
                    "X-Drive {0}: {1} value {2}: Invalid length {3}: 1 to 1024",
                    sXDriveLabel,
                    XDRIVES_KEY_PAGE_BLOB_VHD,
                    sPageBlobVhdName,
                    sPageBlobVhdName.Length
                    );
            }

            /*
             * Validate: Page Blob name's Format
             */
            Regex rPageBlobVhdName = new Regex(REGEX_BLOB_NAME);
            Match matchPageBlobVhdName = rPageBlobVhdName.Match(sPageBlobVhdName);

            if (!matchPageBlobVhdName.Success)
            {
                Trace.TraceWarning(
                    "X-Drive {0}: {1} value {2}: Has invalid characters",
                    sXDriveLabel,
                    XDRIVES_KEY_PAGE_BLOB_VHD,
                    sPageBlobVhdName
                    );
                return false;
            }

            /*
             * Define: Page Blob's path by combining 
             * Blob Container name and Page Blob VHD name
             */
            string sXDrivePath = sBlobContainerName + "/" + sPageBlobVhdName;
            Trace.TraceInformation(
                "X-Drive {0}: Page Blob path: {1}",
                sXDriveLabel,
                sXDrivePath
                );

            /*
             * Configuration: Get Access Mode flag
             */
            if (dicXDriveConfig.ContainsKey(XDRIVES_KEY_ACCESS_MODE))
            {
                string sAccessMode = dicXDriveConfig[XDRIVES_KEY_ACCESS_MODE];

                if (sAccessMode.Equals(ACCESS_MODE_NONE, StringComparison.OrdinalIgnoreCase))
                {
                    eAccessMode = AccessMode.None;
                }
                else if (sAccessMode.Equals(ACCESS_MODE_WRITE, StringComparison.OrdinalIgnoreCase))
                {
                    eAccessMode = AccessMode.Write;
                }
                else if (sAccessMode.Equals(ACCESS_MODE_READONLY, StringComparison.OrdinalIgnoreCase))
                {
                    eAccessMode = AccessMode.ReadOnly;
                }
                else
                {
                    Trace.TraceWarning(
                        "X-Drive {0}: {1} value {2}: Invalid value: [none|write|readonly]",
                        sXDriveLabel,
                        XDRIVES_KEY_ACCESS_MODE,
                        sAccessMode
                        );
                    return false;
                }
            }

            /*
             * Validate: Check if X-Drive is already mounted,
             * If it is already mounted, then igore mounting
             * and continue to next X-Drive definition.
             */
            bool bXDriveMountExists = false;
            if (null != m_aXDrivesPathMounted
                && 0 < m_aXDrivesPathMounted.Count
            ) {
                bXDriveMountExists = m_aXDrivesPathMounted.Contains(sXDrivePath);
                if (bXDriveMountExists)
                {
                    Trace.TraceInformation(
                        "X-Drive {0}: Already mounted",
                        sXDriveLabel
                        );
                    return false;
                }

                Trace.TraceInformation(
                    "X-Drive {0}: Not mounted: {1}",
                    sXDriveLabel,
                    sXDrivePath
                    );
            }

            /*
             * Configuration: Get Create X-Drive if exists
             */
            if (dicXDriveConfig.ContainsKey(XDRIVES_KEY_CREATE_IF_NOT_EXIST))
            {
                bXDriveCreateIfNotExist = dicXDriveConfig[XDRIVES_KEY_CREATE_IF_NOT_EXIST].Equals(ANSWER_YES, StringComparison.OrdinalIgnoreCase);
            }

            /*
             * Configuration: If requesting to create, then 
             * required X-Drive's siz e to create
             */         
            if (bXDriveCreateIfNotExist)
            {
                if (!dicXDriveConfig.ContainsKey(XDRIVES_KEY_SIZE_MB))
                {
                    Trace.TraceWarning(
                        "X-Drive {0}: Create requires {1}: {2} MB to {3} MB",
                        sXDriveLabel,
                        XDRIVES_KEY_SIZE_MB,
                        MIN_XDRIVES_SIZE_MB,
                        MAX_XDRIVES_SIZE_MB
                        );
                    return false;
                }

                /*
                 * Configuration: Get X-Drive size to create
                 */
                if (dicXDriveConfig.ContainsKey(XDRIVES_KEY_SIZE_MB))
                {
                    string sValue = dicXDriveConfig[XDRIVES_KEY_SIZE_MB];
                    iXDriveSizeMB = Convert.ToInt32(sValue);

                    if (iXDriveSizeMB < MIN_XDRIVES_SIZE_MB || iXDriveSizeMB > MAX_XDRIVES_SIZE_MB)
                    {
                        Trace.TraceWarning("X-Drive {0}: {1} value {2}: Invalid size: {3} MB to {4} MB",
                                sXDriveLabel,
                                XDRIVES_KEY_SIZE_MB,
                                iXDriveSizeMB,
                                MIN_XDRIVES_SIZE_MB,
                                MAX_XDRIVES_SIZE_MB
                                );
                        return false;
                    }
                }
            }

            /*
             * Configuration: Get Mount X-Drive with this Read Cache Size
             */     
            if (dicXDriveConfig.ContainsKey(XDRIVES_KEY_MOUNT_CACHE_SIZE_MB))
            {
                string sValue = dicXDriveConfig[XDRIVES_KEY_MOUNT_CACHE_SIZE_MB];
                iXDriveMountCacheSizeMB = Convert.ToInt32(sValue);

                if (iXDriveMountCacheSizeMB < 0
                    || iXDriveMountCacheSizeMB > m_iMaximumSizeInMegabytes
                )
                {
                    Trace.TraceWarning("X-Drive {0}: {1}: Invalid size {2}: 0 to {3}",
                        sXDriveLabel,
                        XDRIVES_KEY_MOUNT_CACHE_SIZE_MB,
                        iXDriveMountCacheSizeMB,
                        m_iMaximumSizeInMegabytes
                        );
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Gather all blobs within Page Blob Container
        /// </summary>
        /// <param name="oBlobContainer"></param>
        /// <returns>ICollection of Blob Names</returns>
        private ICollection<string> GetBlobContainerListing(CloudBlobContainer oBlobContainer)
        {
            Trace.TraceInformation("Gather all blobs within {0}", oBlobContainer.Name);
            ICollection<string> aPageBlobs = new Collection<string>();

            IEnumerable<IListBlobItem> blobList = oBlobContainer.ListBlobs();
            foreach (IListBlobItem blobItem in blobList)
            {
                string sBlobUri = blobItem.Uri.ToString();
                Trace.TraceInformation("Blob: {0}", sBlobUri);
                aPageBlobs.Add(sBlobUri);
            }

            return aPageBlobs;
        }

        /// <summary>
        /// Return a collection of currently mounted x-drives' URI strings.
        /// </summary>
        /// <returns>ICollection of Blob URIs</returns>
        private ICollection<string> GetMountedDrivesPaths()
        {
            Trace.TraceInformation("Get path listing of all currently mounted X-Drives");
            IDictionary<string, Uri> dicMountedDrives = CloudDrive.GetMountedDrives();
            if (null == dicMountedDrives || 0 == dicMountedDrives.Count)
            {
                Trace.TraceInformation("No X-Drives are currently mounted");
                return null;
            }

            ICollection<Uri> aMountUris = dicMountedDrives.Values;
            ICollection<string> aMountXDrivePaths = new Collection<string>();
            foreach (Uri uriMountPath in aMountUris)
            {
                string sPageBlobContainer = String.Empty;
                string sPageBlobVHD = String.Empty;
                bool bReadOnly = false;

                XDrives.GetPathPageBlobVHD(
                        uriMountPath,
                        ref sPageBlobContainer,
                        ref sPageBlobVHD,
                        ref bReadOnly
                        );

                string sMountPath = String.Format("{0}/{1}", sPageBlobContainer, sPageBlobVHD);
                Trace.TraceInformation("Mounted {0}", sMountPath);
                aMountXDrivePaths.Add(sMountPath);
            }

            return aMountXDrivePaths;
        }

        /// <summary>
        /// Return a collection of currently mounted x-drives' URI strings.
        /// </summary>
        /// <returns>ICollection of Blob URIs</returns>
        private ICollection<string> GetMountedDrivesURIs()
        {
            IDictionary<string, Uri> dicMountedDrives = CloudDrive.GetMountedDrives();
            if (null == dicMountedDrives || 0 == dicMountedDrives.Count)
            {
                return null;
            }

            ICollection<string> aMountUriStrings = new Collection<string>();
            foreach (Uri uriMountPath in dicMountedDrives.Values)
            {
                aMountUriStrings.Add(uriMountPath.ToString());
            }

            return aMountUriStrings;
        }

        /// <summary>
        /// Read X-Drives configuration settings from ServiceConfiguration.cscfg
        /// </summary>
        /// <returns>X-Drives Configuration String</returns>
        public string ReadXDrivesConfig()
        {
            string sXDrivesConfig = string.Empty;
            try
            {
                sXDrivesConfig = RoleEnvironment.GetConfigurationSettingValue(XDRIVES_CONFIG_SETTING);

                if (null != sXDrivesConfig || string.Empty != sXDrivesConfig)
                {
                    sXDrivesConfig = sXDrivesConfig.Trim();
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError(
                    "X-Drives: Unexpected Exception: Failed Read X-Drives Config: {0}",
                    ex.Message
                    );
            }

            return sXDrivesConfig;
        }

        /// <summary>
        /// Parse returned mounting configuration requests for XDrives
        /// </summary>
        /// <param name="sXDrivesConfigs">string</param>
        /// <returns>Collection defining each X-Drive configuration</returns>
        public ICollection<IDictionary<string, string>> ParseXDriveConfigs(string sXDrivesConfigs)
        {
            ICollection<IDictionary<string, string>> aXDrivesConfigs = new Collection<IDictionary<string, string>>();

            char[] aCloudDrivesConfigsDelims = { ';' };
            string[] aXDrivesConfigsSplit = sXDrivesConfigs.Split(aCloudDrivesConfigsDelims);

            foreach (string sXDriveConfigRaw in aXDrivesConfigsSplit)
            {
                string sXDriveConfigTrim = " []";
                char[] aXDriveConfigTrim = sXDriveConfigTrim.ToCharArray();
                string sXDriveConfig = sXDriveConfigRaw.Trim(aXDriveConfigTrim);

                if (0 == sXDriveConfig.Length)
                {
                    continue;
                }

                char[] aXDriveConfigKeyValuesDelims = { ',' };
                string[] aXDriveConfigKeyValues = sXDriveConfig.Trim().Split(aXDriveConfigKeyValuesDelims);

                IDictionary<string, string> dicXDriveConfigKeyValue = new Dictionary<string, string>();

                foreach (string sXDriveConfigKeyValue in aXDriveConfigKeyValues)
                {
                    string sXDriveConfigKeyValueDelimChars = "=";
                    char[] aXDriveConfigKeyValueDelims = sXDriveConfigKeyValueDelimChars.ToCharArray();
                    string[] aXDriveConfigKeyValue = sXDriveConfigKeyValue.Trim().Split(aXDriveConfigKeyValueDelims);

                    string sXDriveConfigKey = aXDriveConfigKeyValue[0].Trim();
                    string sXDriveConfigValue = aXDriveConfigKeyValue[1].Trim();

                    if (0 == sXDriveConfigKey.Length)
                    {
                        Trace.TraceWarning("XDrive configuration has an undefined key",
                                sXDriveConfigKey
                                );
                        continue;
                    }

                    if (dicXDriveConfigKeyValue.ContainsKey(sXDriveConfigKey))
                    {
                        Trace.TraceWarning("XDrive configuration has a duplicate key {0}",
                                sXDriveConfigKey
                                );
                        continue;
                    }

                    if (0 == sXDriveConfigValue.Length)
                    {
                        Trace.TraceWarning("XDrive configuration has undefined value for key {0}",
                                sXDriveConfigKey
                                );
                        continue;
                    }

                    dicXDriveConfigKeyValue.Add(sXDriveConfigKey, sXDriveConfigValue);
                }

                aXDrivesConfigs.Add(dicXDriveConfigKeyValue);
            }

            return 0 == aXDrivesConfigs.Count ? null : aXDrivesConfigs;
        }

        /// <summary>
        /// Get cloud storage account based upon storage connection string.
        /// </summary>
        /// <returns>True upon success, else False</returns>
        private bool GetCloudStorageAccount()
        {
            string sStorageConnection = RoleEnvironment.GetConfigurationSettingValue(SETTING_STORAGE_ACCOUNT_INFO).Trim();
            if (null == sStorageConnection || 0 == sStorageConnection.Length)
            {
                Trace.TraceWarning("Service Configuration Setting {0} not defined", SETTING_STORAGE_ACCOUNT_INFO);
                return false;
            }

            m_oCloudStorageAccount = null;
            if (sStorageConnection.Equals("UseDevelopmentStorage=true"))
            {
                m_oCloudStorageAccount = CloudStorageAccount.DevelopmentStorageAccount;
            }
            else
            {
                char[] aStorageCredentialsDelims = { ';' };
                string[] aStorageCredentials = sStorageConnection.Split(aStorageCredentialsDelims);

                IDictionary<string, string> dicStorageCredentials = new Dictionary<string, string>();
                foreach (string sStorageCredential in aStorageCredentials)
                {
                    char[] aStorageCredentialDelims = { '=' };
                    string[] aStorageCredential = sStorageCredential.Split(aStorageCredentialDelims);

                    dicStorageCredentials.Add(aStorageCredential[0].Trim(), aStorageCredential[1].Trim());
                }

                StorageCredentialsAccountAndKey oStorageCredentialsAccountAndKey
                    = new StorageCredentialsAccountAndKey(
                        dicStorageCredentials["AccountName"], 
                        dicStorageCredentials["AccountKey"]
                        );

                bool bUseHttps = false;
                if (dicStorageCredentials.ContainsKey("DefaultEndpointsProtocol"))
                {
                    string sDefaultEndpointsProtocol = dicStorageCredentials["DefaultEndpointsProtocol"].Trim();
                    bUseHttps = sDefaultEndpointsProtocol.Equals("https", StringComparison.OrdinalIgnoreCase);
                }

                m_oCloudStorageAccount = new CloudStorageAccount(oStorageCredentialsAccountAndKey, bUseHttps);
            }

            if (null == m_oCloudStorageAccount)
            {
                Trace.TraceWarning("Unable to create cloud storage account");
                return false;
            }

            return true;
        }

        /// <summary>
        /// Attempt to perform mount of X-Drive
        /// </summary>
        /// <param name="oCloudDrive">Cloud Drive reference</param>
        /// <param name="sXDriveLabel">X-Drive Label</param>
        /// <param name="iXDriveMountCacheSizeMB">X-Drive mount cache size MB</param>
        /// <param name="eAccessMode"></param>
        /// <param name="bReDo">Reference Redo mount attempt</param>
        /// <returns>True if no error has occurred, else False</returns>
        private bool DoCloudDriveMount(
            CloudDrive oCloudDrive,
            string sXDriveLabel,
            int iXDriveMountCacheSizeMB,
            AccessMode eAccessMode,
            ref bool bReDo
        )
        {
            /*
             * A mount is made readonly if requested by X-Drive
             * configuration or X-Drive is already mounted.
             */
            if (AccessMode.ReadOnly == eAccessMode)
            {
                CloudDrive oCloudDriveSnapshot = null;
                if (!CreateXDriveSnapshot(
                        oCloudDrive, 
                        sXDriveLabel,
                        ref oCloudDriveSnapshot
                        ))
                {
                    return false;
                }

                oCloudDrive = oCloudDriveSnapshot;
            }

            if (!DoAttemptXDriveMount(
                    oCloudDrive, 
                    sXDriveLabel, 
                    iXDriveMountCacheSizeMB, 
                    eAccessMode, 
                    ref bReDo
                    )
            ) {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Create Blob Container that will hold Page Blob VHDs
        /// </summary>
        /// <param name="sXDriveLabel">Friendly X-Drive Label</param>
        /// <param name="sBlobContainerName">Blob Container Name</param>
        /// <returns>True upon success, else False</returns>
        private bool CreateXDriveBlobContainer(
            string sXDriveLabel,
            string sBlobContainerName
        ) {
            try
            {
                m_blobClient
                    .GetContainerReference(sBlobContainerName)
                    .CreateIfNotExist();
            }
            catch (StorageClientException ex)
            {
                Trace.TraceError(
                    "X-Drive {0}: StorageClientException: Failed to create Page Blob Container {1}: {2}",
                    sXDriveLabel,
                    sBlobContainerName,
                    ex.Message
                    );
                return false;
            }
            catch (Exception ex)
            {
                Trace.TraceError(
                    "X-Drive {0}: Unexpected Exception: Failed to create Page Blob Container {1}: {2}",
                    sXDriveLabel,
                    sBlobContainerName,
                    ex.Message
                    );
                return false;
            }

            return true;
        }

        /// <summary>
        /// Create an X-Drive
        /// </summary>
        /// <param name="oCloudDrive">Cloud Drive reference</param>
        /// <param name="sXDriveLabel">Friendly X-Drive Label</param>
        /// <param name="sPageBlobVhdName">Page Blob VHD name</param>
        /// <param name="iXDriveSizeMB">Size of X-Drive in MBs</param>
        /// <returns>True upon success, else False</returns>
        private bool CreateXDrive(
            CloudDrive oCloudDrive,
            string sXDriveLabel,
            string sPageBlobVhdName,
            int iXDriveSizeMB
        ) {
            bool bSuccess = false;
            try
            {
                Trace.TraceInformation(
                    "X-Drive {0}: Creating drive and its associated page blob {1}",
                    sXDriveLabel,
                    sPageBlobVhdName
                    );
                oCloudDrive.Create(iXDriveSizeMB);
                bSuccess = true;
            }
            catch (CloudDriveException)
            {
                /*
                 * Ignore: Throws this exception if drive already exists.
                 */
                bSuccess = true;
            }
            catch (Exception ex)
            {
                Trace.TraceError(
                    "X-Drive {0}: Unexpected Exception: Failed to create drive: {1}",
                    sXDriveLabel,
                    ex.Message
                    );
            }

            return bSuccess;
        }

        /// <summary>
        /// Create snapshot of X-Drive
        /// </summary>
        /// <param name="oCloudDrive">Cloud Drive reference</param>
        /// <param name="sXDriveLabel">X-Drive Label</param>
        /// <param name="oCloudDriveSnapshot">Snapshot of Cloud Drive</param>
        /// <returns>True if no error has occurred, else False</returns>
        private bool CreateXDriveSnapshot(
            CloudDrive oCloudDrive,
            string sXDriveLabel,
            ref CloudDrive oCloudDriveSnapshot
        ) {
            try
            {
                Trace.TraceInformation(
                    "X-Drive {0}: Creating snapshot",
                    sXDriveLabel
                    );

                Uri uriSnapshot = oCloudDrive.Snapshot();
                oCloudDriveSnapshot = m_oCloudStorageAccount.CreateCloudDrive(
                    uriSnapshot.ToString()
                );
            }
            catch (CloudDriveException ex)
            {
                Trace.TraceError(
                    "X-Drive {0}: CloudDriveException: Failed to create snapshot: {1}",
                    sXDriveLabel,
                    ex.Message
                    );

                return false;
            }
            catch (Exception ex)
            {
                Trace.TraceError(
                    "X-Drive {0}: Unexpected Exception: Failed to create snapshot: {1}",
                    sXDriveLabel,
                    ex.Message
                    );
                return false;
            }

            return true;
        }

        /// <summary>
        /// Attempt to perform mount of X-Drive
        /// </summary>
        /// <param name="oCloudDrive">Cloud Drive reference</param>
        /// <param name="sXDriveLabel">X-Drive Label</param>
        /// <param name="iXDriveMountCacheSizeMB">X-Drive mount cache size MB</param>
        /// <param name="eAccessMode"></param>
        /// <param name="bReDo">Reference Redo mount attempt</param>
        /// <returns>True if no error has occurred, else False</returns>
        private bool DoAttemptXDriveMount(
            CloudDrive oCloudDrive,
            string sXDriveLabel,
            int iXDriveMountCacheSizeMB,
            AccessMode eAccessMode,
            ref bool bReDo
        ) {
            try
            {
                string sAccessMode = string.Empty;
                DriveMountOptions option = DriveMountOptions.None;
                switch (eAccessMode)
                {
                    case AccessMode.ReadOnly:
                        sAccessMode = "ReadOnly";
                        break;
                    case AccessMode.Write:
                        sAccessMode = "Write";
                        option = DriveMountOptions.Force;
                        break;
                    case AccessMode.None:
                    default:
                        sAccessMode = "Default";
                        break;
                }

                Trace.TraceInformation("X-Drive {0}: Mounting: Mode \"{1}\"",
                    sXDriveLabel,
                    sAccessMode
                    );

                string sDriveLetter
                    = oCloudDrive.Mount(iXDriveMountCacheSizeMB, option);

                Trace.TraceInformation(
                    "X-Drive {0}: Mounted: Letter \"{1}\": Mode \"{2}\", Uri \"{3}\"",
                    sXDriveLabel,
                    sDriveLetter,
                    sAccessMode,
                    oCloudDrive.Uri.ToString()
                    );
            }
            catch (CloudDriveException ex)
            {
                if (ex.Message.Equals("ERROR_LEASE_LOCKED"))
                {
                    if (AccessMode.ReadOnly != eAccessMode)
                    {
                        Trace.TraceInformation("X-Drive {0}: Mount attempt had lease locked. Redo in mode ReadOnly.",
                            sXDriveLabel
                            );

                        bReDo = true;
                        return true;
                    }

                    return false;
                }
                else
                {
                    Trace.TraceError(
                        "X-Drive {0}: CloudDriveException: Failed to mount: {1}",
                        sXDriveLabel,
                        ex.Message
                        );
                    return false;
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError(
                    "X-Drive {0}: Unexpected Exception: Failed to mount: {1}",
                    sXDriveLabel,
                    ex.Message
                    );
                return false;
            }

            return true;
        }

        /// <summary>
        /// Determine the sum of all mount cache requests.
        /// </summary>
        /// <param name="aXDriveConfigs">Configuration of all X-Drives requested</param>
        /// <returns>Sum of X-Drives' Mount Read Caches</returns>
        private static int GetSumXDrivesMountCache(ICollection<IDictionary<string, string>> aXDriveConfigs)
        {
            int iSumXDrivesMountCache = 0;
            foreach (IDictionary<string, string> dicXDriveConfig in aXDriveConfigs)
            {
                if (dicXDriveConfig.ContainsKey(XDRIVES_KEY_MOUNT_CACHE_SIZE_MB))
                {
                    string sValue = dicXDriveConfig[XDRIVES_KEY_MOUNT_CACHE_SIZE_MB];
                    int iXDriveMountCacheSizeMB = Convert.ToInt32(sValue);
                    iSumXDrivesMountCache += iXDriveMountCacheSizeMB;
                }
            }

            return iSumXDrivesMountCache;
        }

        /// <summary>
        /// Find X-Drive Label based upon Page Blob Container name
        /// and Page Blob VHD name.
        /// </summary>
        /// <param name="aXDriveConfigs"></param>
        /// <param name="sPageBlobContainer">Page Blob Container name</param>
        /// <param name="sPageBlobVHD">Page Blob VHD name</param>
        /// <returns>X-Drive Label</returns>
        public static string GetXDriveLabel(
            ICollection<IDictionary<string, string>> aXDriveConfigs,
            string sPageBlobContainer,
            string sPageBlobVHD
            )
        {
            string sXDriveLabel = String.Empty;

            foreach (IDictionary<string, string> dicXDriveConfig in aXDriveConfigs)
            {
                string sPageBlobContainerValue = dicXDriveConfig[XDRIVES_KEY_BLOB_CONTAINER];
                if (!sPageBlobContainer.Equals(sPageBlobContainerValue, StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                string sPageBlobVhdValue = dicXDriveConfig[XDRIVES_KEY_PAGE_BLOB_VHD];
                if (!sPageBlobVHD.Equals(sPageBlobVhdValue, StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                sXDriveLabel = dicXDriveConfig[XDRIVES_KEY_LABEL];
                break;
            }

            return sXDriveLabel;
        }

        /// <summary>
        /// Get Page Blob VHD Path Parts.
        /// </summary>
        /// <param name="uri">Page Blob VHD URI</param>
        /// <param name="sPageBlobContainer">OUT Page Blob Container name</param>
        /// <param name="sPageBlobVHD">Page Blob VHD name</param>
        public static void GetPathPageBlobVHD(
            Uri uri,
            ref string sPageBlobContainer,
            ref string sPageBlobVHD,
            ref bool bReadOnly
            )
        {
            sPageBlobContainer = String.Empty;
            sPageBlobVHD = String.Empty;

            string sPath;
            string sPathAndQuery = uri.PathAndQuery;
            string sQuery = uri.Query;

            if (null != sQuery && sQuery.Length > 0)
            {
                sPath = sPathAndQuery.Substring(0, sPathAndQuery.Length - sQuery.Length);
                bReadOnly = sQuery.StartsWith("?snapshot");
            }
            else
            {
                sPath = sPathAndQuery;
                bReadOnly = false;
            }

            char[] aPathDelims = { '/' };
            sPath = sPath.TrimStart(aPathDelims);

            if (sPath.StartsWith(DEV_STORAGE_ACCT_PREFIX))
            {
                sPath = sPath.Substring(DEV_STORAGE_ACCT_PREFIX.Length);
                sPath = sPath.TrimStart(aPathDelims);
            }

            int iPageBlobContainerDelim = sPath.IndexOf('/');
            sPageBlobContainer = sPath.Substring(0, iPageBlobContainerDelim);
            sPageBlobVHD = sPath.Substring(iPageBlobContainerDelim + 1);
        }

        /// <summary>
        /// Parse letter from drive letter
        /// </summary>
        /// <param name="sDriveLetter">Drive Letter</param>
        /// <returns>Letter</returns>
        public static string GetDriveLetter(string sDriveLetter)
        {
            char[] aDelims = { ':' };
            return sDriveLetter.TrimEnd(aDelims);
        }

        /// <summary>
        /// Sets Environment variable X_Drives with currently mounted
        /// X-Drives
        /// </summary>
        public void SetXDrivesEnvironmentVariable()
        {
            /*
             * X-Drives configuration string
             * from ServiceConfiguration.cscfg file.
             */
            string sXDrivesConfig = ReadXDrivesConfig();
            if (null == sXDrivesConfig || 0 == sXDrivesConfig.Length)
            {
                return;
            }

            /*
             * Parse provide XDrives string from Service Configuration file.
             */
            ICollection<IDictionary<string, string>> aXDriveConfigs = ParseXDriveConfigs(sXDrivesConfig);
            if (null == aXDriveConfigs || 0 == aXDriveConfigs.Count)
            {
                Trace.TraceInformation("X-Drives: None are currently defined to be mounted.");
                return;
            }

            IDictionary<String, Uri> listDrives = CloudDrive.GetMountedDrives();
            if (0 == listDrives.Count)
            {
                Trace.TraceInformation("X-Drives: None are currently mounted");
                return;
            }

            string[] aXDrives = new string[listDrives.Count];

            int i = 0;
            foreach (KeyValuePair<String, Uri> kvpDrive in listDrives)
            {
                string sXDriveLetter = XDrives.GetDriveLetter(kvpDrive.Key);
                Uri uriXDrivePath = kvpDrive.Value;

                string sXDriveLabel;
                string sPageBlobContainer = String.Empty;
                string sPageBlobVHD = String.Empty;
                bool bReadOnly = false;

                XDrives.GetPathPageBlobVHD(
                        uriXDrivePath,
                        ref sPageBlobContainer,
                        ref sPageBlobVHD,
                        ref bReadOnly
                        );

                sXDriveLabel
                    = XDrives.GetXDriveLabel(
                        aXDriveConfigs,
                        sPageBlobContainer,
                        sPageBlobVHD
                        );

                string sAccess = bReadOnly ? "r" : "w";
                aXDrives[i] = String.Format("{0}=[{1},{2}]", sXDriveLabel, sXDriveLetter, sAccess);
                i++;
            }

            if (0 < aXDrives.Length)
            {
                string sXDriveEnv = String.Join(";", aXDrives);

                Trace.TraceInformation("{0} = {1}", XDRIVES_ENV_VAR, sXDriveEnv);

                Environment.SetEnvironmentVariable(XDRIVES_ENV_VAR, sXDriveEnv);
            }
        }
    }
}
